﻿#include "Texture.h"

#include <iostream>

Texture::Texture(
    const std::string &path,
    uint32_t target,
    uint32_t internalFormat,
    uint32_t format,
    uint32_t type,
    bool generateMipMap,
    int32_t maxLod)
    : Asset(path),
      m_target(target),
      m_internalFormat(internalFormat),
      m_format(format),
      m_type(type),
      m_generateMipMap(generateMipMap),
      m_maxLod(maxLod)
{
  Generate();

  LoadTexture2D(path);
}

Texture::~Texture()
{
  if (IsValid())
    glDeleteTextures(1, &m_textureID);
}

void Texture::Generate(uint32_t target, uint32_t internalFormat, uint32_t format, uint32_t type, bool generateMipMap, int32_t maxLod)
{
  m_target = target;
  m_internalFormat = internalFormat;
  m_format = format;
  m_type = type;
  m_generateMipMap = generateMipMap;
  m_maxLod = maxLod;

  if (IsValid())
    glDeleteTextures(1, &m_textureID);

  Generate();
}

void Texture::Generate()
{
  assert(m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_2D_ARRAY || m_target == GL_TEXTURE_CUBE_MAP);
  glGenTextures(1, &m_textureID);
  Bind();

  SetFilterParam();
  SetWrapParam();

  if (m_generateMipMap)
  {
    glTexParameteri(m_target, GL_TEXTURE_MAX_LOD, m_maxLod);
  }
  Unbind();
}

void Texture::Buffer2DRGBAData(const Image &image)
{
  assert(m_target == GL_TEXTURE_2D);
  Bind();
  glTexImage2D(m_target, 0, m_internalFormat, image.width, image.height,
               0, m_format, m_type, image.data.data());
  if (m_generateMipMap)
    glGenerateMipmap(m_target);
  Unbind();
}

void Texture::BufferCubeMapData(std::array<Image, 6> &data)
{
  assert(m_target == GL_TEXTURE_CUBE_MAP);
  Bind();
  for (int i = 0; i < 6; i++)
  {
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, m_internalFormat, data[i].width, data[i].height, 0, m_format, m_type, data[i].data.data());
  }
  if (m_generateMipMap)
    glGenerateMipmap(m_target);
  Unbind();
}

void Texture::LoadTexture2D(const std::string &path)
{
  std::string absPath = (std::filesystem::current_path() / path).generic_string();
  Image image;
  image.LoadFromFile(absPath);
  Buffer2DRGBAData(image);
}

void Texture::LoadTextureCubeMap(const std::string &path)
{
  std::stringstream parts(path);

  std::array<Image, 6> images{};
  std::string imageName;
  for (int32_t i = 0; i < 6; i++)
  {
    if (!std::getline(parts, imageName, ';'))
    {
      std::cerr << "Invalid cube map name format" << std::endl;
      return;
    }
    images[i].LoadFromFile(imageName);
  }

  BufferCubeMapData(images);
}

void Texture::Bind() const
{
  glBindTexture(m_target, m_textureID);
}

void Texture::BindToSlot(unsigned int slot) const
{
  glActiveTexture(GL_TEXTURE0 + slot);
  Bind();
}

void Texture::Unbind() const
{
  glBindTexture(m_target, 0);
}

void Texture::Allocate(int width, int height)
{
  assert(m_target == GL_TEXTURE_2D);
  Bind();
  m_width = width;
  m_height = height;

  glTexImage2D(m_target, 0, m_internalFormat, width, height, 0, m_format, m_type, nullptr);
  if (m_generateMipMap)
    glGenerateMipmap(m_target);

  Unbind();
}

void Texture::SetWrapParam(GLenum s, GLenum t)
{
  glBindTexture(m_target, m_textureID);
  glTexParameteri(m_target, GL_TEXTURE_WRAP_S, s);
  glTexParameteri(m_target, GL_TEXTURE_WRAP_T, t);
}

void Texture::SetFilterParam(GLenum min, GLenum mag)
{
  glBindTexture(m_target, m_textureID);
  glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, m_generateMipMap ? GL_NEAREST_MIPMAP_LINEAR : min);
  glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, mag);
}